import asyncio
from contextlib import asynccontextmanager
from datetime import datetime, timedelta
import os
from unittest import IsolatedAsyncioTestCase
from urllib.parse import urljoin, quote

import httpx
import requests
from p_config import Config
import websockets
import logging.config
from async_timeout import timeout

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
logging.config.fileConfig(os.path.join(BASE_DIR, 'logging.conf'))
logger = logging.getLogger('app')

# yaml_config_name = 'pro.yaml'
yaml_config_name = 'default.yaml'


def load_config():
    test_config_file = os.environ.get('TEST_CONFIG', yaml_config_name)
    test_config_file = os.path.join(BASE_DIR, test_config_file)
    config = Config(test_config_file)
    return config


config = load_config()
unit_id = config['UNIT.ID']
logger.info(f'{unit_id=}')
api_http_protocol = config['NDMOOC_SERVER.HTTP_PROTOCOL']
api_host = config['NDMOOC_SERVER.HOST']
api_url = f'{api_http_protocol}//{api_host}'

account_protocol = config['ACCOUNT_SERVER.HTTP_PROTOCOL']
account_host = config['ACCOUNT_SERVER.HOST']
account_url = f'{account_protocol}//{account_host}'

cc_host = config['BROKER_SERVER.HOST']
cc_http_protocol = config['BROKER_SERVER.HTTP_PROTOCOL']
cc_ws_protocol = config['BROKER_SERVER.WS_PROTOCOL']
cc_http_url = f'{cc_http_protocol}//{cc_host}'
cc_ws_url = f'{cc_ws_protocol}//{cc_host}'


@asynccontextmanager
async def mytimeout(second, func_name=None):
    try:
        async with timeout(second):
            yield
    except asyncio.TimeoutError as exc:
        print(f'----------{exc},{func_name=} ')
        raise asyncio.TimeoutError(f'----------{exc},{func_name=} ')


class DotDict(dict):
    def __getattr__(self, key):
        return self[key]

    def __setattr__(self, key, value):
        self[key] = value


def join_url_parmas(base_url, url, params=None):
    url = urljoin(base_url, url)
    if params:
        params_str = '&'.join(f'{key}={quote(value)}'
                              for key, value in params.items())
        url += '?' + params_str
    return url


class HTTPClient:
    def __init__(self, config=None):
        self.config = config or load_config()
        self.session = httpx.AsyncClient()

    async def close(self):
        await self.session.aclose()

    @asynccontextmanager
    async def request(self, method, url, *args, **kwargs):
        kwargs.setdefault("timeout", 10)
        resp = await self.session.request(method, url, *args, **kwargs)
        try:
            yield resp
        except Exception as err:
            summary = f'{method.upper()} {resp.status_code} ' \
                      f'{url} \nresponse: {resp.text}'
            logger.error(summary)
            raise err
        finally:
            pass

    async def __aenter__(self):
        return self

    async def __aexit__(self, *exc):
        await self.close()


class WebsocketClient:
    def __init__(self, websocket_kwargs=None, config=None):
        self.config = config or load_config()
        self.websocket_kwargs = websocket_kwargs or {}
        self.websocket = None
        self.url = None

    async def __aenter__(self):
        self.websocket = await websockets.connect(
            self.url,
            **self.websocket_kwargs,
        )
        return self

    async def __aexit__(self, *exc):
        if self.websocket:
            await self.websocket.close()


class NDMoocAPI(HTTPClient):
    """api.ndmooc.com  PHP 编写的后台接口
    """

    def __init__(self, config=None):
        super().__init__(config=config)
        self.account_url = account_url

    async def get_access_token(self, username, password, client_id):
        url = 'v1/users/login'
        url = urljoin(self.account_url, url)
        data = {
            'loginid': username,
            'password': password,
            'client_id': client_id,
            'response_type': 'token',  # 返回类型。支持：token、cookie
        }
        async with self.request('POST', url, data=data) as resp:
            json_resp = resp.json()
            logger.info(f"access_token={json_resp['data']['access_token']}")
            return json_resp['data']

    async def add_unit(self, course_id, token):
        # 创建单元
        url = '/v1/units/add'
        url = urljoin(api_url, url)
        params = {'token': token}
        now = datetime.now()
        unit_title = '测试' + str(now)
        _format = '%Y-%m-%d %H:%M:%S'
        data = {
            'course_id': course_id,
            'title': unit_title,
            'start_time': now.strftime(_format),
            'end_time': (now + timedelta(hours=12)).strftime(_format),
            'oid': 1,
        }
        async with self.request('POST', url, params=params, json=data) as resp:
            unit_id = resp.json()['data']['unit_id']

        # 获取单元详情
        url = f'/v1/units/{unit_id}/status'
        url = urljoin(api_url, url)
        params = {
            'cc': 1,
            'token': token,
        }
        data = {'status': 1}
        async with self.request('POST', url, params=params, json=data) as resp:
            pass
        return unit_id


class BrokerAPI(HTTPClient):
    """中控提供的HTTP接口
    """

    def __init__(self, config=None):
        super().__init__(config=config)
        self.account_url = account_url
        self.cc_url = cc_http_url
        self.unit_id = unit_id

    async def get_user_num(self, unit_id, access_token):
        """获取在线用户列表
        """
        url = f"/v2/units/{unit_id}/users"
        url = urljoin(cc_http_url, url)
        params = {'token': access_token}
        resp = await self.session.get(url, params=params)
        return resp.json()['data']['total']

    async def instructions_add(self, unit_id, access_token, json_data):
        """HTTP指令开放接口
        """
        url = "/v2/openapi/instructions/add"
        url = urljoin(self.cc_url, url)
        print(f'--{self.cc_url=}-')
        print(f'--{url=}-')
        params = {'unit_id': unit_id, 'token': access_token}
        resp = await self.session.post(url, params=params, json=json_data)
        print(f'-------------{resp=}')
        print(f'-------------{resp.text=}')
        return resp.text

    async def get_latest_active_time(self, access_token, teacher=False):
        """单元最新活跃时间
        """
        url = "/v2/units/latest/active/time"
        url = urljoin(self.cc_url, url)
        params = {'token': access_token, 'unit_id': unit_id}
        if teacher:
            params['type'] = 1
        print(f'----{params=}---')
        resp = await self.session.get(url, params=params)
        print(f'-------------{resp.text=}')
        return resp.json()['data']

    async def get_unit_id_reset(self, access_token):
        """单元重置
        """
        url = f"/v2/units/{unit_id}/reset"
        url = urljoin(self.cc_url, url)
        params = {'token': access_token}
        print(f'----{params=}---')
        print(f'----{url=}---')
        resp = await self.session.get(url, params=params)
        print(f'-------------{resp.text=}')
        return


class BrokerWebSocket(WebsocketClient):
    """中控的Websocket接口
    """

    def __init__(self, unit_id, params=None, config=None, **kwargs):
        super().__init__(self, config=config)
        url = f'v2/ngx/center/units/{unit_id}'
        self.url = join_url_parmas(cc_ws_url, url, params)
        logger.info(f'WebsocketClient{self.url=}')
        self.websocket = None
        self.websocket_kwargs = kwargs


class TeacherTestCase(IsolatedAsyncioTestCase):
    # 老师的测试用例，用于获取token和添加测试单元

    @classmethod
    def setUpClass(cls):
        config = load_config()
        loop = asyncio.get_event_loop()
        ncmooc_api = NDMoocAPI(config=config)
        # 创建测试单元
        # task = ncmooc_api.add_unit('', token)
        # cls.unit_id = loop.run_until_complete(task)
        cls.unit_id = unit_id
        # 获取用户的 token
        # ------------teacher 1-----------------
        cls.teacher = DotDict()
        teacher_info = config['ACCOUNT.TEACHER_LIST'][0]
        task = ncmooc_api.get_access_token(
            teacher_info['username'],
            teacher_info['password'],
            teacher_info['client_id'],
        )
        teacher_data = loop.run_until_complete(task)
        cls.token = cls.teacher.token = teacher_data['access_token']
        cls.teacher.info = teacher_info
        cls.teacher.info.update(teacher_data)
        # ------------teacher 2-----------------
        cls.teacher2 = DotDict()
        teacher2_info = config['ACCOUNT.TEACHER_LIST'][1]
        task = ncmooc_api.get_access_token(
            teacher2_info['username'],
            teacher2_info['password'],
            teacher2_info['client_id'],
        )
        teacher2_data = loop.run_until_complete(task)
        cls.teacher2.token = teacher2_data['access_token']
        cls.teacher2.info = teacher2_info
        cls.teacher2.info.update(teacher2_data)
        # ------------student 1-----------------
        cls.student = DotDict()
        student_info = config['ACCOUNT.STUDENT_LIST'][0]
        student_task = ncmooc_api.get_access_token(
            student_info['username'],
            student_info['password'],
            student_info['client_id'],
        )
        student_data = loop.run_until_complete(student_task)
        cls.student.token = student_data['access_token']
        cls.student.info = student_info
        cls.student.info.update(teacher2_data)
        # 关闭session
        loop.run_until_complete(ncmooc_api.close())


class TokenAbout:
    def __init__(self, unit_id=unit_id, config=None):
        self.request = requests.request
        self.config = config or load_config()
        self.cc_url = cc_http_url
        self.account_url = account_url
        self.teacher_A = self.config['ACCOUNT.TEACHER_LIST'][0]
        self.teacher_B = self.config['ACCOUNT.TEACHER_LIST'][1]
        self.student = self.config['ACCOUNT.TEACHER_LIST'][1]
        self.teacher_A_info = {}
        self.teacher_B_info = {}
        self.unit_id = unit_id

    def request(self, method, url, **kwargs):
        resp = self.request(method, url, **kwargs)
        return resp.json()

    def get_access_token(self, username, password, client_id):
        _url = 'v1/users/login'
        url = urljoin(self.account_url, _url)
        data = {
            'loginid': username,
            'password': password,
            'client_id': client_id,
            'response_type': 'token',  # 返回类型。支持：token、cookie
        }
        resp = self.request('POST', url, data=data)
        json_resp = resp.json()
        return json_resp['data']  # ['access_token']

    def unit_status_start(self, unit_id, token):
        # 开课
        url = f'/v1/units/{unit_id}/status'
        url = urljoin(api_url, url)
        params = {
            'cc': 1,
            'token': token,
        }
        data = {'status': 1}
        resp = self.request('POST', url, data=data)
        json_resp = resp.json()
        print(f'unit_status_start,{url=}')
        print(f'unit_status_start,{params=}')
        print(f'unit_status_start,{json_resp=}')
        return json_resp

    def save_token(self):
        self.teacher_B_info = self.get_access_token(
            self.teacher_B['username'],
            self.teacher_B['password'],
            self.teacher_B['client_id'],
        )
        self.teacher_A_info = self.get_access_token(
            self.teacher_A['username'],
            self.teacher_A['password'],
            self.teacher_A['client_id'],
        )

    def get_user_num(self, unit_id, access_token, teacher=False):
        """获取在线用户列表 (在线终端列表
        """
        url = f"/v2/units/{unit_id}/users"
        url = urljoin(self.cc_url, url)
        print(f'--{self.cc_url=}-')
        print(f'--{url=}-')
        params = {'token': access_token}

        print(f'get_user_num__{url=}')
        print(f'get_user_num__{access_token=}')
        resp = self.request('GET', url, params=params)
        print(f'-----------{resp.text=}')
        return resp.json()['data']['total']

    def get_user_num_data(self, unit_id, access_token, teacher=False):
        """获取在线用户列表 (在线终端列表
        """
        url = f"/v2/units/{unit_id}/users"
        url = urljoin(self.cc_url, url)
        print(f'--{self.cc_url=}-')
        print(f'--{url=}-')
        params = {'token': access_token}

        print(f'get_user_num__{url=}')
        print(f'get_user_num__{access_token=}')
        resp = self.request('GET', url, params=params)
        print(f'-----------{resp.text=}')
        return resp.json()['data']

    def get_chat_message(self, unit_id, access_token):
        """
            文字消息列表
            /v2/units/:unit_id/chat/message
            ?token=:token&chat_id=:id&limit=:limit
        """
        url = f"/v2/units/{unit_id}/chat/message"
        url = urljoin(self.cc_url, url)
        print(f'--{self.cc_url=}-')
        print(f'--{url=}-')
        params = {'token': access_token}
        print(f'get_user_num__{url=}')
        print(f'get_user_num__{access_token=}')
        resp = self.request('GET', url, params=params)
        print(f'-----------{resp.json()=}')
        return resp.json()['data']

    def get_chat_image(self, unit_id, access_token):
        """
            图片消息列表
            /v2/units/:unit_id/chat/image?token=:token&limit=:limit
        """
        url = f"/v2/units/{unit_id}/chat/image"
        url = urljoin(self.cc_url, url)
        print(f'--{self.cc_url=}-')
        print(f'--{url=}-')
        params = {'token': access_token}
        print(f'get_user_num__{url=}')
        print(f'get_user_num__{access_token=}')
        resp = self.request('GET', url, params=params)
        print(f'-----------{resp.json()=}')
        return resp.json()['data']

    def get_interaction_members(self, unit_id, access_token):
        """
            互动成员列表
            /v2/units/:unit_id/interaction/members?token=:access_token
        """
        url = f"/v2/units/{unit_id}/interaction/members"
        url = urljoin(self.cc_url, url)
        print(f'--{self.cc_url=}-')
        print(f'--{url=}-')
        params = {'token': access_token}
        print(f'get_user_num__{url=}')
        print(f'get_user_num__{access_token=}')
        resp = self.request('GET', url, params=params)
        print(f'-----------{resp.content=}')
        return resp.json()['data']

    def get_forbid_talkers(self, unit_id, access_token):
        """
            禁言成员列表
            /v2/units/:unit_id/forbid_talkers?token=:token
        """
        url = f"/v2/units/{unit_id}/forbid_talkers"
        url = urljoin(self.cc_url, url)
        print(f'--{self.cc_url=}-')
        print(f'--{url=}-')
        params = {'token': access_token}
        print(f'get_user_num__{url=}')
        print(f'get_user_num__{access_token=}')
        resp = self.request('GET', url, params=params)
        print(f'-----------{resp.content=}')
        return resp.json()['data']

    def get_latest_active_time(self, unit_id, access_token, teacher=False):
        """
            单元最新活跃时间
            /v2/units/latest/active/time?unit_id=:unit_id
        """
        url = "/v2/units/latest/active/time"
        url = urljoin(self.cc_url, url)
        print(f'--{self.cc_url=}-')
        print(f'--{url=}-')
        params = {'unit_id': unit_id, 'token': access_token}
        if teacher:
            params['type'] = 1
        print(f'get_user_num__{url=}')
        print(f'get_user_num__{params=}')
        resp = self.request('GET', url, params=params)
        print(f'-----------{resp.content=}')
        return resp.json()['data']
